/*
 * Copyright (c) 2012-2013 XXXX XXXXXX, Inc.
 * XXXX Road, XXXX, XXXX State, XXXX Post code, XXXX Country.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of XXXX
 * XXXXXX, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with XXXX.
 * 
 * History
 * Date        Ver Author        Change Description
 * ----------- --- ------------- ----------------------------------------
 * 04 Jun 2012 001 Karl          Create
 * 20 Jun 2012 002 Karl          Chrome browser driver.switchTo().frame(String)
 *                               not work.
 * 27 Nov 2012 003 Karl          clear() and sendKeys() cause IE browser go into
 *                               background and fail to set value correctly. tune
 *                               sendKeys() for IE.  
 * 30 Nov 2012 004 Karl          1. Add function to wait for a list to be stable
 * 								 2. Double click on an element. 
 * 13 Dec 2012 005 Karl          Chrome does not provide By information when 
 *                               NoSuchElementException is thrown. give some.    
 * 14 Dec 2012 006 Karl          Proxy wrongly wrapped object calling exception.
 * 26 Dec 2012 007 Karl          Wrap selenium WebDriverWait class.       
 * 06 Jan 2013 008 Karl          Add method to call window dos command
 * 07 Jan 2013 009 Karl          Can load user defined firefox profile 
 * 05 Feb 2013 010 Karl          Use running limitation to save startup browser info
 * 19 Mar 2013 011 Karl          calling clear() might cause IE minimized, fix it
 * 20 Mar 2013 012 Karl          calling select(...) might cause IE minimized, fix it
 * 02 Apr 2013 013 Karl          seleniumUtil.waitForPageToLoad() failed sometimes, but the screen shot 
 *                               seems fine, track this problem.
 * 09 Apr 2013 014 Karl          Try andriod web app auto test.
 * 04 Jun 2013 015 Karl          Make getScreenShot check if a alert window is present.
 * 17 Jun 2013 016 Karl          Wait for element to load completely can set a timeout argument
 * 27 Jun 2013 017 Karl          Add mapping methods of alert and window.
 * 06 Jul 2013 018 Karl          default waitForPageToLoad(...) not printStackTrace
 */

package com.selenium.test.util;

import static junit.framework.Assert.assertEquals;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NoSuchFrameException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverBackedSelenium;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WebDriver.TargetLocator;
import org.openqa.selenium.android.AndroidDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.firefox.internal.ProfilesIni;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.selenium.test.base.Page.WaitForPageToLoadStrategy;
import com.selenium.test.util.RunningLimitation.RequestedRunningLimitation;
import com.selenium.test.util.SeleniumAntRptAttachmentHandler.AntAttachment;
import com.thoughtworks.selenium.SeleniumException;

public class SeleniumUtil implements ISeleniumUtil{
	/*-----------------------1. Variables And Constructors--------------------*/
	/** iterative checking sleep time, default 1 second*/
	private static final long DEFAULT_WAIT_IN_MILLISECONDS = 1000;
	
	/** sleep time sum limit, default 10 seconds*/
	public static final int DEFAULT_TIMEOUT_IN_MILLISECONDS = 
		Integer.parseInt(SeleniumConfigHolder.readDefaultWaitForPageToLoad()); 
	
	/** 
	 * make private selenium to PREVENT it from being used(anywhere use 
	 * SeleniumUtil instead)
	 */
	private WebDriverBackedSelenium selenium;
	
	/** 
	 * make private driver to PREVENT it from being used(anywhere use 
	 * SeleniumUtil instead)
	 */
	private WebDriver driver;
	
	/** Actual running limitation applied on current test script */
	private RunningLimitation actualRunningLimitation;
	
	/** default web site URL when starting up a browser*/
	private String host = System.getProperty("host");
	
	/** default web site port number when starting up a browser*/
	private String port = System.getProperty("port");
	
	/** if driver is in a unstable state. */
	private boolean isInUnstableState = false;
	
	/** screen shot type info*/
	public static String SHOT_TYPE_INFO_STR = "info";
	
	/** screen shot type error*/
	public static String SHOT_TYPE_ERR_STR = "error";
	
	/** screen shot type failure*/
	public static String SHOT_TYPE_FAIL_STR = "fail";
	
	/**
	 * <font color="red"><b>!!! IMPORTANT NOTEICE !!! </b></font><br/> <br/>
	 * 
	 * if in audit mode, it will count the time used to execute command. 
	 * <font color="red">DO NOT</font> set this switch on in production 
	 * environment ! it is for tuning performance only! 
	 */
	private static final boolean auditTimeMode = 
			SeleniumConfigHolder.readRunInAuditTimeMode();
	
	/** make an instance of current selenium util*/
	private static ISeleniumUtil newInstance_(List<RunningLimitation> requiredRunningLimitations, 
			RequestedRunningLimitation requestedRunningLimitation, RunningLimitation actualRunningLimitation){
		SeleniumUtil seleniumUtil = new SeleniumUtil();
		seleniumUtil.actualRunningLimitation = actualRunningLimitation;
		
		// if use Selenium Grid, ask web driver from grid hub
		WebDriver driver ;
		if(!actualRunningLimitation.getUseSeleniumGrid()){
			// Although client request using grid, but test script writer had limited this script
			// to run on local machine, so local browser is used. 
			if(requestedRunningLimitation.getUseSeleniumGrid()){
				System.out.println("Test script writer had limited this test script to run only on local machine. " +
					"So the Selenium Grid option was ignored directly, and current test was going to run on local " +
					"machine.");
			}
			driver = seleniumUtil.selectDriver();
		} else {
			String gridHostUrl = SeleniumConfigHolder.getSeleniumGridUrl();
			driver = seleniumUtil.selectDriver(gridHostUrl);
		}
		WebDriverBackedSelenium selenium = new WebDriverBackedSelenium(driver, 
			"http://" + seleniumUtil.getHost() + ":" + seleniumUtil.getPort());
		seleniumUtil.selenium = selenium;
		seleniumUtil.driver = driver;
		
		ISeleniumUtil rtn = seleniumUtil;
		// if time pause feature is set on, pause a while after a method calling
		if(readPauseAfterEachStep() > 0){
			rtn = (ISeleniumUtil)Proxy.newProxyInstance(
					SeleniumUtil.class.getClassLoader(), 
					SeleniumUtil.class.getInterfaces(), 
					new SeleniumUtilOperationPauser(rtn, readPauseAfterEachStep()));
		}
		
		// if time audit feature is set on
		if(auditTimeMode) {
			rtn = (ISeleniumUtil)Proxy.newProxyInstance(
					SeleniumUtil.class.getClassLoader(),
					SeleniumUtil.class.getInterfaces(), 
					new SeleniumUtilTimeCostPrinter(rtn));
		}
		return rtn;
	}
	
	/** If test is configured to be in audit mode, time cost of starting a 
	 * browser is also recorded. */
	public static ISeleniumUtil newInstance(List<RunningLimitation> requiredRunningLimitations, 
			RequestedRunningLimitation requestedRunningLimitation, RunningLimitation actualRunningLimitation
		){
		ISeleniumUtil rtn = null; 
		
		AuditTimeClass auditTime = null;
		if(auditTimeMode){
			auditTime = new AuditTimeClass("newInstance(" +	
					actualRunningLimitation.getBrowserName() + ")", "newInstance");
		}
		
		try{
			rtn = newInstance_(requiredRunningLimitations, requestedRunningLimitation, actualRunningLimitation);
		} finally{
			if(auditTimeMode && auditTime != null){
				// make sure to release AuditTimeClass - it is in seperate thread
				double costSeconds = auditTime.endAudit();
				String costSecondsStr = auditTime.getCostSecondsStr();
				if(costSeconds > SeleniumUtilTimeCostPrinter.WARNING_SECONDS){
					System.err.format(SeleniumUtilTimeCostPrinter.WARNING_MSG_FORMAT, 
						costSecondsStr,	
						String.valueOf(SeleniumUtilTimeCostPrinter.WARNING_SECONDS));
				}
			}
		}
		return rtn;
	}
	
	
	/*-----------------------2. Methods---------------------------------------*/
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getDriver()
	 */
	public WebDriver getDriver(){
		return driver;
	}
	
	/** 
	 * if use grid feature, will ask driver from grid hud instead of create 
	 * it directly. 
	 * 
	 */
	private WebDriver selectDriver(String remoteAddressStr) {
		// load extra properties
		loadBrowserProperties(getBrowserName());
		
		// get driver
		DesiredCapabilities capability;
		WebDriver rtn;
		
		if (Browsers.INTERNETEXPLORER.equals(getBrowserName())){
			capability = DesiredCapabilities.internetExplorer();
	    } else if (Browsers.FIREFOX.equals(getBrowserName())){
			capability = DesiredCapabilities.firefox();	
		} else if (Browsers.SAFARI.equals(getBrowserName())) {
			capability = DesiredCapabilities.safari();
		} else if (Browsers.CHROME.equals(getBrowserName())){
			capability = DesiredCapabilities.chrome();
		} else {
			capability = DesiredCapabilities.firefox();
		}
		try {
			if(getBrowserShortName() != null){
				capability.setBrowserName(getBrowserShortName());
			}
			if(getBrowserVersion() != null){
				capability.setVersion(getBrowserVersion());
			}
			if(getBrowserPlatform() != null){
				capability.setPlatform(Platform.valueOf(getBrowserPlatform()));
			}
			rtn = new RemoteWebDriver(
				new URL(remoteAddressStr), capability);
		} catch (MalformedURLException e) {
			e.printStackTrace();
			rtn = null;
		}
		return rtn;
	}
	
	/** create a WebDriver by browser name
	 *
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 07 Jan 2013 009 Karl          Can load user defined firefox profile 
	 * 09 Apr 2013 014 Karl          Try andriod web app auto test.
	 * 
	 * @param browserVersion not yet used now.
	 * @param browserPlatform not yet used now.
	 **/
	private WebDriver selectDriver() {
    	// load extra properties
		loadBrowserProperties(getBrowserName());
		
		// get driver
		if (Browsers.INTERNETEXPLORER.equals(getBrowserName())){
        	return new InternetExplorerDriver();
        }
		else if (Browsers.FIREFOX.equals(getBrowserName())){
			FirefoxProfile profile = null;
			if(System.getProperty(SeleniumConfig.TEST_FIREFOX_PROFILE_NAME_KEY) != null){
				profile = new ProfilesIni().getProfile(System.getProperty(SeleniumConfig.TEST_FIREFOX_PROFILE_NAME_KEY));
			}
			
			if(profile != null){
				return new FirefoxDriver(profile);  
			} else {
				return new FirefoxDriver();
			}
		}
		else if (Browsers.SAFARI.equals(getBrowserName())) {
			// not use selenium 1, use selenium 2 instead.
			/*DesiredCapabilities capabilities = new DesiredCapabilities();
			capabilities.setBrowserName("safari");
			CommandExecutor executor = null;
			try {
				executor = new SeleneseCommandExecutor(new URL("http://" + 
							getHost() + ":" + getSeleniumPort()),
							new URL("http://"+ getHost() + ":" + getPort()),
							capabilities);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
			return new RemoteWebDriver(executor, capabilities);*/
			return new SafariDriver();
		} 
		else if (Browsers.CHROME.equals(getBrowserName())){
			return new ChromeDriver();
		} else if (Browsers.WEBVIEW.equals(getBrowserName())){
			return new AndroidDriver();
		}
        return new FirefoxDriver();	
	}
	
	/** load extra properties for specific browser*/
	private void loadBrowserProperties(String browser){
		try {
        	if (Browsers.INTERNETEXPLORER.equals(browser)){
            	SeleniumConfigHolder.loadIEProperties();
            }
    		else if (Browsers.FIREFOX.equals(browser)){
    			SeleniumConfigHolder.loadFirefoxProperties();
    		}
    		else if (Browsers.SAFARI.equals(browser)) {
    			SeleniumConfigHolder.loadSafariProperties();
    		} 
    		else if (Browsers.CHROME.equals(browser)){
    			SeleniumConfigHolder.loadChromeProperties();
    		}	
		} catch (IOException e) {
			// no need extra operations
			e.printStackTrace();
		}
	}

	private String getHost() {
		if (host == null)
		 host = "localhost"; 
		return host;
	}

	private String getPort() {
		if (port == null)
			 port = "8081";
		return port;
	}
	
	public String getBrowserShortName(){
		return actualRunningLimitation.getBrowserShortName();
	}
	
	public String getBrowserName() {
		return actualRunningLimitation.getBrowserName();
	}
	
	public String getBrowserVersion(){
		return actualRunningLimitation.getBrowserVersion();
	}
	
	public String getBrowserPlatform(){
		return actualRunningLimitation.getBrowserPlatform();
	}
	
	public String getBrowserFolderName(){
		return actualRunningLimitation.getBrowserFolderName();
	}
	
	private boolean isExecuted(IsMethod isMethod, By by) {
		long timer = 0;
		while (true) {
			try {
				if ((IsMethod.IS_ENABLED.equals(isMethod) && 
						findElement(by).isEnabled())
					|| (IsMethod.IS_DISPLAYED.equals(isMethod) && 
						findElement(by).isDisplayed())
					|| (IsMethod.IS_SELECTED.equals(isMethod) && 
						findElement(by).isSelected())) {
					return true;
				}
				else {
					Thread.sleep(DEFAULT_WAIT_IN_MILLISECONDS);
				}
			} catch (Exception e) {
			}
			timer += DEFAULT_WAIT_IN_MILLISECONDS;
			if (timer > DEFAULT_TIMEOUT_IN_MILLISECONDS) {
				break;
			}
		}
		return false;
	}
	
	private boolean isExecuted(IsMethod isMethod, WebElement element){
		long timer = 0;
		while (true) {
			try {
				if ((IsMethod.IS_ENABLED.equals(isMethod) && 
						element.isEnabled())
					|| (IsMethod.IS_DISPLAYED.equals(isMethod) && 
						element.isDisplayed())
					|| (IsMethod.IS_SELECTED.equals(isMethod) && 
						element.isSelected())) {
					return true;
				}
				else {
					Thread.sleep(DEFAULT_WAIT_IN_MILLISECONDS);
				}
			} catch (Exception e) {
			}
			timer += DEFAULT_WAIT_IN_MILLISECONDS;
			if (timer > DEFAULT_TIMEOUT_IN_MILLISECONDS) {
				break;
			}
		}
		return false;
	}

	/* 
	 * wrap Selenium2 methods, later just make SeleniumUtil to control browser, 
	 * not selenium object or driver object. <br/>
	 * 
	 * since get(baseUrl) might blocked by NON-response site, force it to wait
	 * and quit in specified time period. 
	 * 
	 */
	public final void get(String baseUrl) {
		
		TimeRecorder recorder = new TimeRecorder();
		
		// get may locked if site no response, force it to wait & quit
		long defaultWaitForPageToLoad = DEFAULT_TIMEOUT_IN_MILLISECONDS;
		if(defaultWaitForPageToLoad <= 0){
			defaultWaitForPageToLoad = 0;
		}
		try {
			Thread loadThread = new GetBaseUrlThread(Thread.currentThread(), 
					driver, baseUrl);
			loadThread.start();
			Thread.sleep(defaultWaitForPageToLoad); 
			if(loadThread.isAlive()){
				try{
					loadThread.interrupt();
				} catch (Exception e){
					// do nothing, let load thread quit quietly
				}
				/* web driver(browser) is hanged now, following operation on driver 
				 * is meaningless, tell seleniumUtil that it is not stable to do
				 * nothing. We are assuming that TimeoutException will be caught
				 * and test is going to exit, nothing except getScreepShot is 
				 * called later. Tell getScreenShot to ignore screen also, since
				 * screen shot also depends on driver. 
				 */
				setInUnstableState(true);
				throw new TimeoutException("Failed to get page within " + 
						(defaultWaitForPageToLoad / 1000) + " seconds.");
			}
		} catch (InterruptedException e) {
			// do nothing
		}
		pauseAfterEachStep(recorder);
	}

	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#quit()
	 */
	public final void quit() {
		driver.quit();
	}

	/** 
	 * <PRE>
	 * 
	 * Find element 
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 13 Dec 2012 005 Karl          Chrome(selenium 2.25.0) does not provide By information when 
	 *                               NoSuchElementException is thrown. give some. 
	 * </PRE>                                                                                                                
	 */
	public final WebElement findElement(By by) {
		WebElement rtn = null;
		try{
			rtn = driver.findElement(by);
		} catch (NoSuchElementException e){
			if(isChrome()){
				throw new NoSuchElementException("Can't locate element: " + by + ". " + e.getMessage());
			} else {
				throw e;
			}
		}
		return rtn;
	}
	
	public final WebElement findElement(String message, By by) {
		WebElement rtn = null;
		try{
			rtn = driver.findElement(by);
		} catch(NoSuchElementException e){
			throw new NoSuchElementException(message, e);
		}
		return rtn;
	}
	
	/** 
	 * <PRE>
	 * 
	 * Find element 
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 13 Dec 2012 005 Karl          Chrome(selenium 2.25.0) does not provide By information when 
	 * </PRE>                        NoSuchElementException is thrown. give some.                                                           
	 */
	@Override
	public final WebElement findElement(WebElement element, By by){
		WebElement rtn = null;
		try{
			rtn = element.findElement(by);
		} catch (NoSuchElementException e){
			if(isChrome()){
				throw new NoSuchElementException("Can't locate child element: " + by + ". " + e.getMessage());
			} else {
				throw e;
			}
		}
		return rtn;
		
	}

	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#findElements(org.openqa.selenium.By)
	 */
	public final List<WebElement> findElements(By by) {
		return driver.findElements(by);
	}
	
	public final List<WebElement> findElements(WebElement parent, By by) {
		return parent.findElements(by);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getLinkByAnchorText(java.lang.String)
	 */
	public final WebElement getLinkByAnchorText(String linkText){
		return driver.findElement(By.xpath("//a[text()=\"" + linkText + "\"]"));
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getCurrentUrl()
	 */
	public final String getCurrentUrl() {
		return driver.getCurrentUrl();
	}

	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getPageSource()
	 */
	public final String getPageSource() {
		return driver.getPageSource();
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getTitle()
	 */
	public final String getTitle() {
		return driver.getTitle();
	}

	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#manage()
	 */
	public final WebDriver.Options manage() {
		return driver.manage();
	}
	
	/** 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 27 Nov 2012 001 Karl          special handling for IE browser. 
	 *                               In web driver official web site, it was 
	 *                               reported that clear() operation will cause
	 *                               IE go into background & lose focus. this 
	 *                               will cause same pre-filled fields failed to
	 *                               clear. So every time before sendKeys is 
	 *                               sent to IE browser, clear() again first.
	 *                               current IE version: IE8, 8.0.6001.18702
	 *                               selenium: 2.25.0, OS: XP. 
	 *                               If this problem is fixed by selenium or 
	 *                               ieserverdriver.exe or what ever, this ugly 
	 *                               calling could be removed then.
	 * 19 Mar 2013 011 Karl          calling clear() might cause IE minimized, fix it
	 */
	public final void sendKeys(WebElement element, String keys) {
		element.sendKeys(keys);
	}
	
	/** 
	 * Clear first and then input keys. Normally use this method to clearly send keys to a input. Whenever 
	 * this not works for IE browser or cause IE minimized, use the specialClearSendKeys instead. 
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 19 Mar 2013 011 Karl          calling clear() might cause IE minimized, fix it
	 */
	public final void clearSendKeys(WebElement element, String keys){
		element.clear();
		element.sendKeys(keys);
	}
	
	/** 
	 * <PRE>
	 * 20 Mar 2013 special clear and send keys.
	 * 
	 * The "special logic" is : 
	 * (1) For IE, clear content by java script, then sendKeys directly. (Because SOME clear() on input cause 
	 *     IE minimized. Only sendKeys() is enough.) This is just a temporary solution, if selenium solve this
	 *     problem in future. We can then just use clear() & sendKeys directly. 
	 * </PRE> 
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 19 Mar 2013 011 Karl          calling clear() might cause IE minimized, fix it
	 */
	public final void specialClearSendKeys(WebElement element, String keys){
		if(!isIE()){
			clearSendKeys(element, keys);
		} else {
			execJavaScript("arguments[0].value='';", element);
			element.sendKeys(keys);
		}
	}
	
	public final void clear(WebElement element) {
		element.clear();
	}
	
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#click(org.openqa.selenium.WebElement)
	 */
	public final void click(WebElement element) {
		element.click();
	}
	
	/** select text*/
	public final void select(Select select, String selectStr){
		select.selectByVisibleText(selectStr);
	}

	/**
	 * Special select operation. The select() calling for IE browser sometimes cause IE minimized unexpectedly.
	 * For IE, we need a temporary solution to fix this. Selenium may fixed this in future, we can change to use
	 * a simple selectByVisibleText(...) calling at that time.
	 * 
	 * History
 	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 20 Mar 2013 012 Karl          Create, calling select(...) might cause IE minimized, fix it
	 */
	public final void specialSelect(WebElement select, String selectStr){
		if(isIE()){
			select.click(); // call first to let select dropped down
		}
		new Select(select).selectByVisibleText(selectStr);
		if(isIE()){
			select.click(); // call first collapse dropped down list
		}
	}
	
	/** custom style select control*/
	public final void select(ICustomDivSelect select, String visibleText){
		select.select(visibleText);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#isSafari()
	 */
	public final boolean isSafari(){
		return ( Browsers.SAFARI.equals(getBrowserName()));
	}
	
	public final boolean isIE(){
		return ( Browsers.INTERNETEXPLORER.equals(getBrowserName()));
	}
	
	public final boolean isFirefox(){
		return ( Browsers.FIREFOX.equals(getBrowserName()));
	}
	
	public final boolean isChrome(){
		return ( Browsers.CHROME.equals(getBrowserName()));
	}
	
	/** 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 09 Apr 2013 014 Karl          Create, try andriod web app auto test.
	 **/
	public final boolean isWebview(){
		return ( Browsers.WEBVIEW.equals(getBrowserName()));
	}
	
	/* Not works, may remove it later. 15 Apr 2013
	 */
	public final void fireClick(WebElement element){
		/*
		if(isSafari()){
			//((JavascriptExecutor)driver).executeScript(
			//		"arguments[0].fireEvent('onclick');", element);
			new Actions(driver).click(element).perform();
		} else {
			click(element);
		}*/
		//new Actions(driver).click(element).build().perform();
		click(element);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#close()
	 */
	public final void close(){
		driver.close();
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#isDisplayed(org.openqa.selenium.WebElement)
	 */
	public final boolean isDisplayed(By by) {
		return isExecuted(IsMethod.IS_DISPLAYED, by);
	}
	
	public final boolean isDisplayed(WebElement element) {
		return isExecuted(IsMethod.IS_DISPLAYED, element);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#isEnabled(org.openqa.selenium.WebElement)
	 */
	public final boolean isEnabled(By by) {
		return isExecuted(IsMethod.IS_ENABLED, by);
	}
	
	public final boolean isEnabled(WebElement element) {
		return isExecuted(IsMethod.IS_ENABLED, element);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#isSelected(org.openqa.selenium.WebElement)
	 */
	public final boolean isSelected(By by) {
		return isExecuted(IsMethod.IS_SELECTED, by);
	}
	
	public final boolean isSelected(WebElement element) {
		return isExecuted(IsMethod.IS_SELECTED, element);
	}

	public void waitForPageToLoad() {
		this.waitForPageToLoad(DEFAULT_TIMEOUT_IN_MILLISECONDS);
	}
	
	/** 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 02 Apr 2013 013 Karl          seleniumUtil.waitForPageToLoad() failed sometimes, but the screen shot 
	 *                               seems fine, track this problem.
	 * 09 Apr 2013 014 Karl          Create, try andriod web app auto test.   
	 * 06 Jul 2013 018 Karl          default waitForPageToLoad(...) not printStackTrace  
	 *                          
	 */
	public void waitForPageToLoad(int timeOutInMilliseconds) {
		// Web view encountered time out problem when calling selenium.waitForPageToLoad(...)
		if(!isWebview()){
			long start = System.currentTimeMillis();
			try {
				selenium.waitForPageToLoad(String.valueOf(timeOutInMilliseconds));
			} catch(SeleniumException e) {
				System.out.println("!Warning: selenium waitForPageToLoad(" + timeOutInMilliseconds + ") " +
					"encountered exception one time, this calling cost about " + 
					(System.currentTimeMillis() - start) + " milliseconds. Testing ignored this warning and go " +
					"on testing.");
			}
		}
	}
	
	/** wait for fixed period time by milliseconds*/
	public void waitForFixedMilliseconds(long milliseconds){
		if(milliseconds <= 0) {
			return;
		}
		try {
			Thread.sleep(milliseconds);
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#selectFrame(java.lang.String)
	 */
	public void selectFrame(String locator){
		/* 
		 * chrome browser(19.0.1084.52) frame(String) not work, if frame 
		 * not found, try frame(WebElement) again. 
		 */
		try{
			driver.switchTo().frame(locator);
		} catch (NoSuchFrameException e){
			if(findElements(By.name(locator)).size() > 0){
				driver.switchTo().frame(findElement(By.name(locator)));
			} else if(findElements(By.id(locator)).size() > 0){
				driver.switchTo().frame(findElement(By.id(locator)));
			} else {
				throw e;
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#selectDefaultContent()
	 */
	public void selectDefaultContent(){
		driver.switchTo().defaultContent();
	}
	
	@Override
	public TargetLocator switchTo(){
		return driver.switchTo();
	}
	
	public void switchToWindow(String windowHandler){
		driver.switchTo().window(windowHandler);
	}
	
	public Alert switchToAlert(){
		return driver.switchTo().alert();
	}
	
	/**
	 * It doesn't work for Chrome browser, so for checking convenience, please
	 * don't use chrome(Firefox, IE instead). 
	 * 
	 * According to test result, Firefox 19 & IE9 work fine.
	 * 
	 * */
	public void focusWindow(){
		try {
			execJavaScript("window.focus()");
		} catch(Exception e){ /* do nothing if focus fails */ }
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getWindowHandle()
	 */
	public String getWindowHandle(){
		return driver.getWindowHandle();
	}
	
	public Set<String> getWindowHandles(){
		return driver.getWindowHandles();
	}
	
	public void switchToPromptWindow(Set<String> before, Set<String> after){
		List<String> whs = new ArrayList<String>(after);
		whs.removeAll(before);
		// This is important, it is found that if a modal window is opened by AutoIt tool, 
		// some handles might be empty at the beginning, and change to a valid value later
		after.remove("");
		if(whs.size() > 0){
			switchToWindow(whs.get(0));
		}else{
			throw new WebDriverException("No new window prompted out.");
		}
	}
	
	/** 
	 * Switch to prompted window after wait for some milliseconds
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 07 Jan 2013 009 Karl          make sure the window handles is valid before switch
	 * 
	 * */
	public void switchToPromptedWindowAfterWait(Set<String> before, long waitForMilliseconds){
		final long ONE_ROUND_WAIT = 100;
		Set<String> after = null;
		for(long i=0; i<waitForMilliseconds; i += ONE_ROUND_WAIT){
			after = driver.getWindowHandles();
			// This is important, it is found that if a modal window is opened by AutoIt tool, 
			// some handles might be empty at the beginning, and change to a valid value later
			after.remove("");
			// window really prompted 
			if(after.size() > before.size()){
				break;
			} 
			waitForFixedMilliseconds(ONE_ROUND_WAIT);
		}
		if(after != null && after.size() > before.size()){
			switchToPromptWindow(before, after);
		} else {
			throw new WebDriverException("No new window prompted out after waiting for " + waitForMilliseconds + " milliseconds.");
		}
	}
	
	/** switch to prompted alert window after wait for some milliseconds*/
	public Alert switchToPromptedAlertAfterWait(long waitForMilliseconds)
		throws NoAlertPresentException{
		final long ONE_ROUND_WAIT = 100;
		NoAlertPresentException lastException = null;
		for(long i=0; i<waitForMilliseconds; i += ONE_ROUND_WAIT){

			try{
				Alert alert = driver.switchTo().alert();
				return alert;
			} catch(NoAlertPresentException e){
				lastException = e;
			}
			waitForFixedMilliseconds(ONE_ROUND_WAIT);
		}
		throw lastException;
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#createFileBy(java.net.URL, java.lang.String)
	 */
	public File createFileBy(URL url, String fileName){
		File currentPath;
		try {
			currentPath = new File(url.toURI());
		} catch(URISyntaxException e) {
			currentPath = new File(url.getPath());
		}
		File file = new File(currentPath.getPath(), fileName);
		return file;
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#windowMaximize()
	 */ 
	public void windowMaximize() {
		driver.manage().window().maximize();
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getScreenShot(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	@SuppressWarnings("rawtypes")
	public void getScreenShot(Class cls, String testMethodName, 
			String shotType, String desc) {
		
		/* If seleniumUtil is in unstable state, just ignore screen shot 
		 * directly.
		 */
		if(isInUnstableState()){
			System.out.println("!Warning: web driver is now in unstable state," +
					" screen shot operation is ignored.");
			return;
		}
		
		// in case an alert is present
		try {
			Alert alert = switchToAlert();
			String errMsg = alert.getText();
			alert.accept();
			System.err.println("!!!ERROR: Current page prompted out an error " +
				"alert window and it was caught by getScreenShot method. " +
				"The text is [" + errMsg + "]. Please ask test script writer to " +
				"track and solve it.");
		} catch(NoAlertPresentException e){
			// it is fine no alert is present
		}
		
		String testClassName = cls.getName();
		
		File outPutFile = null;
		try {
			DateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmssSSS");
			String now = fmt.format(new Date());
			// by default use system level getBrowserFolderName to get screen
			// shot file path
			File path = SeleniumConfigHolder.getAntAttachmentFileParent(
					SeleniumConfigHolder.getBrowserFolderName(), testClassName, 
					testMethodName);
			File screenshot = null;
			if (!(driver instanceof TakesScreenshot)) {
				WebDriver augmentDriver = new Augmenter().augment(driver);
				screenshot = ((TakesScreenshot) augmentDriver)
					.getScreenshotAs(OutputType.FILE);
			} else {
				screenshot = ((TakesScreenshot) driver)
					.getScreenshotAs(OutputType.FILE);
			}
			FileInputStream fis;
			if (!path.exists()) {
				path.mkdirs();
			}
			fis = new FileInputStream(screenshot);
			outPutFile = new File(path, now + "_" + shotType + ".png");
			FileOutputStream fos = new FileOutputStream(outPutFile);

			FileChannel fcin = fis.getChannel();
			FileChannel fcout = fos.getChannel();
			fcin.transferTo(0, fcin.size(), fcout);
			fcin.close();
			fcout.close();
			fis.close();
			fos.close();
			
			// register screen shot as test attachment
			AntAttachment atta = new AntAttachment( 
					getBrowserFolderName(),
					Class.forName(testClassName), 
					testMethodName, 
					outPutFile, 
					outPutFile.getName()
					);
			SeleniumAntRptAttachmentHandler.registerAndCopyAttachment(atta);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getScreenShot(java.lang.String, java.lang.String)
	 */
	@SuppressWarnings("rawtypes")
	public void getScreenShot(Class cls, String testMethodName){
		getScreenShot(cls, testMethodName, SHOT_TYPE_INFO_STR, 
				SHOT_TYPE_INFO_STR);
	}
	
	/* (non-Javadoc)
	 * @see com.selenium.test.util.ISeleniumUtil#getScreenShot(java.lang.String, java.lang.String, java.lang.String)
	 */
	@SuppressWarnings("rawtypes")
	public void getScreenShot(Class cls, String testMethodName, 
			String shotType){
		getScreenShot(cls, testMethodName, shotType, shotType);
	}
	
	/** 
	 * get method name which calls this getStackMethodName(int) method
	 */
	public static String getStackMethodName(int previousLevel){
	    // If > 0 , than means go nextCnt more steps to find the method name
		boolean beginCnt = false;
		int nextCnt = ((previousLevel < 0) ? 0 : previousLevel) + 1;
		StackTraceElement[] e = Thread.currentThread().getStackTrace();

		for (StackTraceElement s : e) {
			// go more step base on "getStackMethodName" element
			if (beginCnt && (nextCnt > 0)) {
				if (s.getMethodName().equals("access$3")) {
					// Filter special system functions
				} else if ((--nextCnt) == 0) {
					return s.getMethodName();
				}
			}
			if (s.getMethodName().equals("getStackMethodName") && !beginCnt) {
				beginCnt = true;
			}
		}
		throw new RuntimeException("Error calling getStackMethodName(" + 
				previousLevel + "), no calling method found.");
	 }
	 
	/*
	 * Since this NON-parm version getStackMethodName call another version of
	 * getStackMethodName(int),so go one step further to get the real method
	 * which calls this method
	 */
	public static String getStackMethodName() {
		return getStackMethodName(1);
	}
	
	@Override
	public void waitForPageToLoad(WaitForPageToLoadStrategy waitStrategy) {
		waitStrategy.waitForPageToLoad();
	}
	
	/**
	 * For now, the only situation that cause this is : seleniumUtil.get()
	 * no response but test can not wait for it, test will abort  
	 * and let the browser open.*/
	public void setInUnstableState(boolean isInUnstableState) {
		this.isInUnstableState = isInUnstableState;
	}
	
	public boolean isInUnstableState() {
		return isInUnstableState;
	}
	
	private static long readPauseAfterEachStep(){
		String pause = SeleniumConfigHolder.readPauseAfterEachStep();
		long pauseLong;
		try{
			pauseLong = Long.parseLong(pause);
		} catch (NumberFormatException e){
			pauseLong = 0;
		}
		return (pauseLong >= 0 ? pauseLong : 0);
	}
	
	/** Pause a while after some operations, such as click, sendKeys, etc.*/
	public void pauseAfterEachStep(TimeRecorder recorder){
		long pauseLong = readPauseAfterEachStep();
		// make sure operation time gap >= pauseLong, and not always wait
		if( pauseLong > 0 && recorder.getTimeCost() < pauseLong){
			try{Thread.sleep(pauseLong);} catch(Exception e){};
		}
	}
	
	/** wait until elements until they are available - can be found by 
	 * findElement(...) call */
	@Override
	public void waitOnElements(By[] waitOnLocators, int waitOnLocatorsTimeoutInMilliseconds){
		for(final By by : waitOnLocators){
			(new WebDriverWait(driver, waitOnLocatorsTimeoutInMilliseconds / 1000))
			  .until(new ExpectedCondition<WebElement>(){
				@Override
				public WebElement apply(WebDriver d) {
					return d.findElement(by);
				}
				@Override
				public String toString(){
					return by.toString();
				}});
		}
	}
	
	/** wait on elements to be enabled, typical usage is for waiting on input
	 * to be enabled when page is loading. */
	@Override
	public void waitOnElementsEnabled(By[] waitOnLocators, 
			int waitOnLocatorsTimeoutInMilliseconds){
		waitOnElements(waitOnLocators, waitOnLocatorsTimeoutInMilliseconds);
		for(final By by : waitOnLocators){
			(new WebDriverWait(driver, waitOnLocatorsTimeoutInMilliseconds / 1000))
			  .until(new ExpectedCondition<Boolean>(){
				@Override
				public Boolean apply(WebDriver d) {
					return isEnabled(d.findElement(by));
				}
				@Override
				public String toString(){
					return by.toString();
				}});
		}
	}
	
	@Override
	public void waitOnElements(By[] waitOnLocators) {
		waitOnElements(waitOnLocators, DEFAULT_TIMEOUT_IN_MILLISECONDS);
	}

	@Override
	public void waitOnElementsEnabled(By[] waitOnLocators) {
		waitOnElementsEnabled(waitOnLocators, DEFAULT_TIMEOUT_IN_MILLISECONDS);
		
	}
	
	/*-----------------------3. Auxiliary Classes-----------------------------*/
	/** 
	 * for available checking.
	 */
	private enum IsMethod {
		IS_ENABLED, IS_DISPLAYED, IS_SELECTED
	}
	
	
	/** 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 09 Apr 2013 014 Karl          Try andriod web app auto test.                              
	 */
	public static final class Browsers{
		
		public enum _BROWSERS {InternetExplorer, Firefox, Chrome, Safari, Webview};
		
		public static final String INTERNETEXPLORER = _BROWSERS.InternetExplorer.name();
		public static final String FIREFOX = _BROWSERS.Firefox.name();
		public static final String CHROME = _BROWSERS.Chrome.name();
		public static final String SAFARI = _BROWSERS.Safari.name();
		public static final String WEBVIEW = _BROWSERS.Webview.name();
		
		/** convert "chrome", "firefox", "internet explorer" style 
		 * short browser name to current style browser name*/
		public static String convertToBrowserName(String browserShortName) {
			String tmpName = browserShortName.replaceAll("-", "")
				.replaceAll(" ", "").toUpperCase();
			if(INTERNETEXPLORER.toUpperCase().equals(tmpName)){
				return INTERNETEXPLORER;
			} else if(CHROME.toUpperCase().equals(tmpName)){
				return CHROME;
			} else if(FIREFOX.toUpperCase().equals(tmpName)){
				return FIREFOX;
			} else if(SAFARI.toUpperCase().equals(tmpName)){
				return SAFARI;
			} else if(WEBVIEW.toUpperCase().equals(tmpName)){
				return WEBVIEW;
			} else {
				throw new RuntimeException("Can't find browser name by " +
					"browser short name [" + browserShortName + "]");
			}
		}
		
		/**
		 * 4 Feb 2013
		 * Convert from browser name to browser short name.
		 * */
		public static String convertToBrowserShortName(String browserName){
			if(browserName == null){
				throw new IllegalArgumentException("Can't convert null browserName to a short browser name.");
			}
				
			if(INTERNETEXPLORER.equalsIgnoreCase(browserName)){
				return "internet explorer";
			} else if(CHROME.equalsIgnoreCase(browserName)){
				return "chrome";
			} else if(FIREFOX.equalsIgnoreCase(browserName)){
				return "firefox";
			} else if(SAFARI.equalsIgnoreCase(browserName)){
				return "safari";
			} else if(WEBVIEW.equalsIgnoreCase(browserName)){
				return "webview";
			} else {
				throw new RuntimeException("Can't find browser short name by " +
					"browser name [" + browserName + "]");
			}
		}
	}
	
	/** check if time out, if exceeds DEFAULT_WAIT_FOR_PAGE_TO_LOAD, then yes*/
	public static class GetBaseUrlThread extends Thread{
		Thread mainThread;
		WebDriver driver;
		String baseUrl;
		
		public GetBaseUrlThread(Thread mainThread, WebDriver driver, 
				String baseUrl){
			this.mainThread = mainThread;
			this.driver = driver;
			this.baseUrl = baseUrl;
		}
		
		@Override
		public void run() {
			// core function of this sub thread : navigate to specific URL
			driver.get(baseUrl);
			// force main thread quit sleep
			mainThread.interrupt();
		}
	}
	
	/** 
	 * AuditTimeClass, print time information in regular time period. <br/><br/>
	 * 
	 * Make sure that <font color="red">call endAudit()</font> after new a 
	 * object, or this AuditTimeClass will <font color="red">infinitely</font> 
	 * print time count information. 
	 * */
	public static class AuditTimeClass {
		
		/** suffix name of the operation which is being audited*/
		private String auditNameSuffix;
		
		/** time to start audit*/
		private long start;
		
		/** time to end audit*/
		private long end;
		
		/** how long (in seconds) it takes*/
		private double costSeconds;
		
		/** internal usage, call print() at fixed rate*/
		private Timer timer = null;
		
		public AuditTimeClass(String auditNamePrefix, String auditNameSuffix){
			this.auditNameSuffix = auditNameSuffix;
			
			// record start time
			start = new Date().getTime();
			System.out.println("<< " + auditNamePrefix + " start");
			
			// call
			timer = new Timer();
			timer.scheduleAtFixedRate(
					new TimerTask(){
						private long i = 1;
						@Override
						public void run() {
							System.out.println("   " + (i++) + "...");
						}
						
					}, 1000, 1000);
		}
		
		public String getCostSecondsStr(){
			return new DecimalFormat("0.0").format(costSeconds);
		}
		
		/** Make sure to call this method to record end time, or this object
		 * will infinitely print time count information.
		 */
		public double endAudit(){
			//record end time
			timer.cancel();
			timer = null;
			end = new Date().getTime();
			costSeconds = ( end - start) / 1000.0;
			String costSecondsStr = getCostSecondsStr();
			System.out.println("   " + auditNameSuffix + " end, cost time " + 
				costSecondsStr + " seconds >> \n");
			return costSeconds;
		}
	}
	
	
	/** time recorder*/
	public static class TimeRecorder{
		private long start = 0;
		private long end = 0;
		
		public TimeRecorder(){
			start = new Date().getTime();
		}
		
		public long getTimeCost(){
			end = new Date().getTime();
			return (end - start);
		}
	}
	
	/** 
	 * <PRE>
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------   
	 * 14 Dec 2012 006 Karl          Proxy wrongly wrapped object calling exception.
	 *                               Just throw the target exception is ok.
	 *                               
	 * </PRE>
	 */
	public static class SeleniumUtilOperationPauser implements InvocationHandler{
		
		Object proxiedObj;
		long pauseLong;
		
		public SeleniumUtilOperationPauser(Object proxiedObj, long pauseLong){
			this.proxiedObj = proxiedObj;
			this.pauseLong = pauseLong;
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			
			Object rtn;
			try{
				TimeRecorder recorder = new TimeRecorder();
				rtn =  method.invoke(proxiedObj, args);
				/** Pause a while after some operations, such as click, sendKeys, etc.*/
				if(recorder.getTimeCost() < pauseLong){
					boolean contained = false;
					if(needPausingMethods.contains(method.getName())){
						contained = true;
					} else {
						for(String loopRegx : needPausingMethods){
							if(method.getName().matches(loopRegx)){
								contained = true;
								break;
							}
						}
					}
					if(contained){
						try{Thread.sleep(pauseLong);} catch(Exception e){};
					}
				}
			} catch (InvocationTargetException e) { // just throw the calling exception is ok
			    	throw e.getTargetException();
	        }
			return rtn;
		}
		
		// only these UI operations will be paused after
		private List<String> needPausingMethods = new ArrayList<String>(
			Arrays.asList(new String[]{
				"clear", "click", "close", "quit", "fireClick", "get",
				"select", "sendKeys", "clearSendKeys", "switchTo", "switchToPromptWindow", 
				"waitForFixedMilliseconds", "waitForPageToLoad", 
				"windowMaximize", "switchToPromptedWindowAfterWait", 
				"switchToPromptWindow", "waitForElementsToLoadComplete", 
				"waitForSelectionToLoadCompletely", "waitForExepectedConditionUntilTimeOutIn"})
			);
	}
	
	/** 
	 * 
	 * dynamic proxy used to record time usage - for tuning performance only
	 * 
	 * <PRE>
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------   
	 * 14 Dec 2012 006 Karl          Proxy wrongly wrapped object calling exception.
	 *                               Just throw the target exception is ok.  
	 * </PRE> 
	 *                                                                                                                    
	 */
	public static class SeleniumUtilTimeCostPrinter implements InvocationHandler{
		
		/** time period after which an warning message is to be given*/
		public static int WARNING_SECONDS = 15;
		
		/** warning message format*/
		public static String WARNING_MSG_FORMAT = "!Warning: previous operation " +
				"cost %s seconds, which excees the reasonable limit %s " +
				"seconds.\n";
		
		Object proxiedObj;
		
		public SeleniumUtilTimeCostPrinter(Object proxiedObj){
			this.proxiedObj = proxiedObj;
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			Object rtn;
			AuditTimeClass auditTime = new AuditTimeClass(method.getName() 
					+ "(" +	Arrays.deepToString(args) + ")", method.getName());
			try{
				rtn =  method.invoke(proxiedObj, args);
			} catch (InvocationTargetException e) {
			    	throw e.getTargetException();
			} finally {
				/* make sure to release AuditTimeClass - it is in seperate 
				 * thread
				 */
				double costSeconds = auditTime.endAudit();
				String costSecondsStr = auditTime.getCostSecondsStr();
				if(costSeconds > WARNING_SECONDS){
					System.err.format(WARNING_MSG_FORMAT, costSecondsStr, 
						String.valueOf(WARNING_SECONDS));
				}
			}
			return rtn;
		}
	}

	@Override
	public void assertImgValid(String message, WebElement imgElement) {
		String src = imgElement.getAttribute("src");
		System.out.println("Going to verify image: [" + src + "] at [" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "]");
		int maxMilliseconds = 30000;
		long startTime = System.currentTimeMillis();
		try{
			URLConnection conn = new URL(src).openConnection();
			conn.setConnectTimeout(maxMilliseconds);
			conn.setReadTimeout(maxMilliseconds);
			conn.getInputStream();
		} catch (MalformedURLException e){
			throw new AssertionError(message + " Image url is :[" + src + "]. Image Url format is not correct.");
		} catch(UnknownHostException e){
			throw new AssertionError(message + " Image url is :[" + src + "]. The host name is invalid.");
		} catch (FileNotFoundException e){
			throw new AssertionError(message + " Image url is :[" + src + "]. Can't find image at this Url.");
		} catch (SocketTimeoutException e){
			throw new AssertionError(message + " Failed to find image within " + 
				((System.currentTimeMillis() - startTime)/1000) + " seconds");
		} catch (IOException e){
			throw new AssertionError(message + " IO Exception happened when finding the image.");
		}finally {	
			System.out.println("Verify image: [" + src + "] end at [" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "].");
		}
	}

	@Override
	public void selectFrame(WebElement element) {
		driver.switchTo().frame(element);
	}
	
	/** 
	 * <PRE>
	 * Wait for element to load all items in it, wait for 6 rounds at most, and 
	 * each round 0.5 seconds, and also check if DEFAULT_TIMEOUT is reached. 
	 * 
	 * <font color="red" size="4">NOTE: </font>don't call this method if : 
	 *   (1) You have better solution to determine if all items are loaded. For
	 *       example, if a "Loading..." text or a loading image on current page 
	 *       can be used to determine if a page is still loading, don't use this
	 *       method. Because if there is no record returned, this method itself
	 *       can't determine if the loading process is done.
	 *   (2) You are sure no item will be returned, or it will cause test script 
	 *       wait about DEFAULT_TIMEOUT_IN_MILLISECONDS milliseconds. 
	 * </PRE>
	 * 
     * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 17 Jun 2013 016 Karl          Wait for element to load completely can 
	 *                               set a timeout argument 
	 * */
	public void waitForElementsToLoadCompletely(By list, int waitRound, 
		int waitMillisecondsPerRound, int timeoutInMillisecondsForNoRecord){
		
		final int WAIT_FOR_ROUND = waitRound;
		final int WAIT_FOR_MILLISECONDS = waitMillisecondsPerRound;
		
		long beforeCallTime = System.currentTimeMillis();
		
		// if result list shows, wait for all records to load
		long prevCount = 0;
		int stableRound = 0;
		boolean someRecordsLoaded = false;
		while(true){
			long rowCount = driver.findElements(list).size();
			long endFind = System.currentTimeMillis();
			
			if(endFind - beforeCallTime > DEFAULT_TIMEOUT_IN_MILLISECONDS){
				break;
			//no record return in timeoutInMillisecondsForNoRecord/1000 seconds
			} else if(!someRecordsLoaded && 
				endFind - beforeCallTime > timeoutInMillisecondsForNoRecord){ 
				break;
			}
			
			if(prevCount == 0 && rowCount > 0){
				someRecordsLoaded = true;
			}
			
			// if record count becomes stable, quit wait, else continue 
			if(rowCount == prevCount && someRecordsLoaded){
				stableRound ++;
				if(stableRound == WAIT_FOR_ROUND) break;
			} else {
				stableRound = 0;
			}
			prevCount = rowCount;
			waitForFixedMilliseconds(WAIT_FOR_MILLISECONDS);
		}
	}
	
	public void waitForElementsToLoadCompletely(By list, int waitRound, 
		int waitMillisecondsPerRound) {
		waitForElementsToLoadCompletely(list, waitRound, 
			waitMillisecondsPerRound, DEFAULT_TIMEOUT_IN_MILLISECONDS);
	}
	
	/**
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 17 Jun 2013 016 Karl          Wait for element to load completely can 
	 *                               set a timeout argument
	 * */
	@Override
	public void waitForSelectionToLoadCompletely(By list, int waitRound,
		int waitMillisecondsPerRound, int timeoutInMillisecondsForNoRecord) {
		final int WAIT_FOR_ROUND = waitRound;
		final int WAIT_FOR_MILLISECONDS = waitMillisecondsPerRound;
		
		long beforeCallTime = System.currentTimeMillis();
		
		// wait for selection to load completely
		long prevCount = 0;
		int stableRound = 0;
		boolean someRecordsLoaded = false;
		while(true){
			long rowCount = new Select(findElement(list)).getOptions().size();
			long endFind = System.currentTimeMillis();
			
			if(endFind - beforeCallTime > DEFAULT_TIMEOUT_IN_MILLISECONDS){
				break;
			//no record return in timeoutInMillisecondsForNoRecord/1000 seconds
			} else if(!someRecordsLoaded && 
				endFind - beforeCallTime > timeoutInMillisecondsForNoRecord){ 
				break;
			}
			
			if(prevCount == 0 && rowCount > 0){
				someRecordsLoaded = true;
			}
			
			// if record count becomes stable, quit wait, else continue 
			if(rowCount == prevCount && someRecordsLoaded){
				stableRound ++;
				if(stableRound == WAIT_FOR_ROUND) break;
			} else {
				stableRound = 0;
			}
			prevCount = rowCount;
			waitForFixedMilliseconds(WAIT_FOR_MILLISECONDS);
		}
	}
	
	@Override
	public void waitForSelectionToLoadCompletely(By list, int waitRound,
		int waitMillisecondsPerRound){
		waitForSelectionToLoadCompletely(list, waitRound, 
			waitMillisecondsPerRound, DEFAULT_TIMEOUT_IN_MILLISECONDS);
	}
	
	/** wait at max WAIT_FOR_ROUND * WAIT_FOR_MILLISECONDS milliseconds to make sure
	 *  not more new windows prompted out*/
	@Override
	public void waitForWindowsToPromptedCompletely(Set<String> before, int waitRound,
			int waitMillisecondsPerRound) {
		final int WAIT_FOR_ROUND = waitRound;
		final int WAIT_FOR_MILLISECONDS = waitMillisecondsPerRound;
		
		long beforeCallTime = System.currentTimeMillis();
		
		long prevCount = before.size();
		int stableRound = 0;
		boolean someWindowsPrompted = false;
		while(true){
			long windowCount = driver.getWindowHandles().size();
			long endFind = System.currentTimeMillis();
			
			if(endFind - beforeCallTime > DEFAULT_TIMEOUT_IN_MILLISECONDS){
				break;
			}
			
			if(windowCount > before.size()){
				someWindowsPrompted = true;
			}
			
			// if record count becomes stable, quit wait, else continue 
			if(windowCount == prevCount && someWindowsPrompted){
				stableRound ++;
				if(stableRound == WAIT_FOR_ROUND) break;
			} else {
				stableRound = 0;
			}
			prevCount = windowCount;
			waitForFixedMilliseconds(WAIT_FOR_MILLISECONDS);
		}
		
		// if no new window prompted, throw assertion error. 
		if(!someWindowsPrompted){
			throw new AssertionError("No new window prompted out even waiting for " + 
					(DEFAULT_TIMEOUT_IN_MILLISECONDS/1000) + "seconds. Please make sure " +
					"network performs ok and test script works fine.");
		}
	}
	
	/**
	 * Now(30 Nov 2012, it works on chrome 22.0.1229.92). 
	 * For other browsers, not yet verified. 
	 * */
	@Override
	public void doubleClick(WebElement element) {
		/* not work ((JavascriptExecutor)driver).executeScript(
				"arguments[0].fireEvent('ondblclick');", element); */
		new Actions(driver).doubleClick(element).perform();
	}
	
	/** 
	 * wait for a expected condition until time out.
	 * 
	 * History
	 * Date        Ver Author        Change Description
	 * ----------- --- ------------- ----------------------------------------
	 * 26 Dec 2012 007 Karl          Wrap WebDriverWait object.
	 */
	@Override
	public void waitForExepectedConditionUntilTimeOutInMilliseconds(
			ExpectedCondition<Boolean> booleanCondition, int timeOutInMilliseconds,
			String message) {
		try{
			new WebDriverWait(driver, timeOutInMilliseconds / 1000).withMessage(message)
				.until(booleanCondition);
		} catch(TimeoutException e){
			e.printStackTrace();
			throw new AssertionError(message);
		}
	}
	
	@Override
	public Object execJavaScript(String javaScript, Object ... params) {
		return ((JavascriptExecutor)driver).executeScript(javaScript, params);
	}
	
	/**
	 * 22 Jan 2013
	 * */
	@Override
	public RunningLimitation getActualRunningLimitation(){
		return new RequestedRunningLimitation(actualRunningLimitation.getUseSeleniumGrid(), 
			actualRunningLimitation.getBrowserPlatform(), actualRunningLimitation.getBrowserShortName(), 
			actualRunningLimitation.getBrowserVersion());
	}
	
	@Override
	public void assertTextEquals(String[] expectedTexts, List<WebElement> list) {
		if(expectedTexts == null || expectedTexts.length ==0){
			throw new IllegalArgumentException("expectedTexts string array is empty.");
		}
		assertEquals(expectedTexts.length, list.size());
		for(int i=0; i<expectedTexts.length; i++){
			assertEquals(expectedTexts[i], list.get(i).getText());
		}
	}
}
